package sucursal;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.joda.time.DateTime;

import ofertas.Oferta;
import clientes.Cliente;
import clientes.ElSaldoDeLaCtaCteEsInsuficienteExeption;
import envio.Envio;
import productos.Presentacion;
import productos.StockNoDisponibleExeption;
import venta.Venta;

public class Sucursal {

	private List<Venta> ventas;
	private List<Presentacion> presentaciones;
	private List<Cliente> clientes;
	private List<Oferta> ofertas;
	private MedioDePago medioDePago;
	private List<Envio> envios;

	/**
	 * Intancia una sucursal inicializando las ventas las presentaciones los
	 * clientes y las ofertas
	 */
	public Sucursal() {

		this.ventas = new LinkedList<Venta>();
		this.presentaciones = new LinkedList<Presentacion>();
		this.clientes = new LinkedList<Cliente>();
		this.ofertas = new LinkedList<Oferta>();
		this.envios = new LinkedList<Envio>();
	}

	/**
	 * Devuelve la lista de Clientes de una sucursal
	 * 
	 * @return List<Cliente>
	 */
	public List<Cliente> clientes() {

		return this.clientes;
	}

	/**
	 * Devuelve la lista de presentaciones de una sucursal
	 * 
	 * @return List<Presentacion>
	 */
	public List<Presentacion> presentaciones() {

		return this.presentaciones;
	}

	/**
	 * Devuelve la lista de ofertas de la sucursal
	 * 
	 * @return List<Oferta>
	 */
	public List<Oferta> ofertas() {

		return this.ofertas;
	}

	/**
	 * Devuelve la lista de ventas de la sucursal
	 * 
	 * @return List<Venta>
	 */
	public List<Venta> ventas() {

		return this.ventas;
	}

	/**
	 * busca una instalcia de cliente en la lista de clientes propia, si el
	 * cliente no esta devuelve una ClienteNoEstaCargadoExeption
	 * 
	 * @param nombre
	 * @param dni
	 * @return
	 * @throws ClienteNoEstaCargadoExeption
	 */
	public Cliente buscarCliente(String nombre, String dni)
			throws ClienteNoEstaCargadoExeption {

		for (Cliente iterableCliente : this.clientes) {
			if ((iterableCliente.nombre() == nombre)
					& (iterableCliente.dni() == dni)) {
				return iterableCliente;
			}
		}
		throw new ClienteNoEstaCargadoExeption();

	}

	/**
	 * La venta directa con cobro en cta cte verifica que los productos existan
	 * en stock, luego delega al medio de pago en cta cte dandole el cliente que
	 * lo busca con el metodo buscarCliente, ademas pasa el los productos y se
	 * pasa la sucursal misma.
	 * 
	 * @param nombre
	 * @param dni
	 * @param productos
	 * @throws StockNoDisponibleExeption
	 * @throws ClienteNoEstaCargadoExeption
	 * @throws ElSaldoDeLaCtaCteEsInsuficienteExeption
	 */
	public void ventaDirectaCobroCtaCte(String nombre, String dni,
			Map<Presentacion, Integer> productos)
			throws ClienteNoEstaCargadoExeption,
			ElSaldoDeLaCtaCteEsInsuficienteExeption {
		
		try {
			this.verificarStock(productos);
			this.pagoEnCtaCte();
			this.medioDePago.cobrar(productos,this.buscarCliente(nombre, dni), this);
		} catch (StockNoDisponibleExeption execption) {
			
			this.ventaDomicilioSinStock(productos, nombre, dni);
		}

	}

	/**
	 * Crea un envio el cual queda como pendiente y se cobra al entregarse
	 * 
	 * @param productos
	 * @param nombre
	 * @param dni
	 * @throws ClienteNoEstaCargadoExeption
	 */
	protected void ventaDomicilioSinStock(Map<Presentacion, Integer> productos,
			String nombre, String dni) throws ClienteNoEstaCargadoExeption {

		DateTime fechaPactada = DateTime.now().plusDays(7);
		Envio nuevoEnvio = new Envio(productos, fechaPactada);
		nuevoEnvio.setCliente(this.buscarCliente(nombre, dni));
		nuevoEnvio.agregarseComoObservadorAStock();
		this.agregarEnvio(nuevoEnvio);

	}

	public void agregarEnvio(Envio nuevoEnvio) {

		this.envios.add(nuevoEnvio);

	}

	/**
	 * setea el medio de pago de la sucursal en una instancia de CobrarEnCtaCte
	 */
	protected void pagoEnCtaCte() {
		this.medioDePago = new CobrarEnCtaCte();

	}

	/**
	 * verifica si cada presentacion que se quiere vender existe en la cantidad
	 * requerida de lo contrario tira una exepcion de tipo
	 * StockNoDisponibleExeption
	 * 
	 * @param productos
	 * @throws StockNoDisponibleExeption
	 */
	protected void verificarStock(Map<Presentacion, Integer> productos)
			throws StockNoDisponibleExeption {

		for (Entry<Presentacion, Integer> presentacionCantidadRequerida : productos
				.entrySet()) {
			presentacionCantidadRequerida.getKey().verificarDisponibilidad(
					presentacionCantidadRequerida.getValue());
		}

	}

	/**
	 * Devuelve el valor de la variable medio de pago de la sucursal
	 * 
	 * @return MedioDePago instance
	 */
	public MedioDePago medioDePago() {

		return this.medioDePago;
	}

	/**
	 * agrega el cliente a la lista de clientes de la sucursal
	 * 
	 * @param unCliente
	 */
	public void agregarCliente(Cliente unCliente) {

		this.clientes.add(unCliente);
	}

	/**
	 * La venta directa con cobro en efectivo con cliente es identica a la venta
	 * con cta corriente solamente que no se le debita de su cuenta , por lo
	 * tanto no lanza la exepcion de cliente no tiene saldo
	 * 
	 * @param Nombre
	 * @param Dni
	 * @param productos
	 * @throws StockNoDisponibleExeption
	 * @throws ClienteNoEstaCargadoExeption
	 * @throws ElSaldoDeLaCtaCteEsInsuficienteExeption
	 */
	public void ventaDirectaCobroEfectivo(String nombre, String dni,
			Map<Presentacion, Integer> productos)
			throws StockNoDisponibleExeption, ClienteNoEstaCargadoExeption,
			ElSaldoDeLaCtaCteEsInsuficienteExeption {
		this.verificarStock(productos);
		this.pagoEnEfectivo();
		this.medioDePago.cobrar(productos, this.buscarCliente(nombre, dni),
				this);
	}

	/**
	 * setea en medio de pago en pago en efectivo para adopatar esa estrategia
	 */
	protected void pagoEnEfectivo() {
		this.medioDePago = new CobrarEnEfectivo();
	}

	/**
	 * chequea el stock y setea el startegy de medio de pago en efectivo, al
	 * cual delega el bro del precio
	 * 
	 * @param productos
	 * @throws StockNoDisponibleExeption
	 * @throws ElSaldoDeLaCtaCteEsInsuficienteExeption
	 */
	public void ventaDirectaCobroEfectivo(Map<Presentacion, Integer> productos)
			throws StockNoDisponibleExeption,
			ElSaldoDeLaCtaCteEsInsuficienteExeption {
		this.verificarStock(productos);
		this.pagoEnEfectivo();
		this.medioDePago.cobrar(productos, this);
	}

	/**
	 * Crea un envio pactado para 2 dias, y lo cobra de la cta cte del cliente
	 * 
	 * @param productos
	 * @param string
	 * @param string2
	 * @throws StockNoDisponibleExeption
	 * @throws ClienteNoEstaCargadoExeption
	 * @throws ElSaldoDeLaCtaCteEsInsuficienteExeption
	 */
	public void ventaDomicilioEnCtaCte(Map<Presentacion, Integer> productos,
			String nombre, String dni) throws StockNoDisponibleExeption,
			ClienteNoEstaCargadoExeption,
			ElSaldoDeLaCtaCteEsInsuficienteExeption {
		this.verificarStock(productos);
		Envio nuevoEnvio = new Envio(productos, DateTime.now().plusDays(2));
		nuevoEnvio.cancelado();
		this.agregarEnvio(nuevoEnvio);
		this.pagoEnCtaCte();
		this.medioDePago.cobrar(productos, this.buscarCliente(nombre, dni),
				this);
	}

	/**
	 * devuelve el valor de la variable envios de la sucursal
	 * 
	 * @return
	 */
	public List<Envio> envios() {

		return this.envios;
	}

	/**
	 * agrega una venta a la lista de ventas
	 * 
	 * @param venta
	 */
	public void agregarVenta(Venta venta) {
		// TODO Auto-generated method stub

	}

	/**
	 * Calcula el valor total de la compra con las ofertas
	 * 
	 * @param productosYCant
	 * @return
	 */
	public Double calcularMontoConOfertas(
			Map<Presentacion, Integer> productosYCant) {
		Double result = 0D;

		for (Entry<Presentacion, Integer> presentacion_cantidad : productosYCant
				.entrySet()) {

			result = result
					+ (this.precioOfertaParaPresentacion(presentacion_cantidad
							.getKey())* presentacion_cantidad.getValue());
		}
		return result;
	}

	protected Double precioOfertaParaPresentacion(Presentacion presentacion) {
		Double result = 0D;
		for (Oferta oferta : this.ofertas()) {
			result += oferta.valorOfertaParaProducto(presentacion);
		};
		return (result * presentacion.precioParaConsumidor());
	}

}
